use tokio_pg_mapper::FromTokioPostgresRow;
use tokio_postgres::{types::ToSql, GenericClient, Statement};

use crate::{error::AppError, Result};

async fn get_stmt(client: &impl GenericClient, sql: &str) -> Result<Statement> {
    let stmt = client.prepare(sql).await.map_err(AppError::from)?;
    Ok(stmt)
}

async fn query<T>(
    client: &impl GenericClient,
    sql: &str,
    params: &[&(dyn ToSql + Sync)],
) -> Result<Vec<T>>
where
    T: FromTokioPostgresRow,
{
    let stmt = get_stmt(client, sql).await?;
    let list = client
        .query(&stmt, params)
        .await
        .map_err(AppError::from)?
        .iter()
        .map(|row| <T>::from_row_ref(row).unwrap())
        .collect::<Vec<T>>();
    Ok(list)
}

async fn query_row_with_msg<T>(
    client: &impl GenericClient,
    sql: &str,
    params: &[&(dyn ToSql + Sync)],
    msg: Option<&str>,
) -> Result<T>
where
    T: FromTokioPostgresRow,
{
    query(client, sql, params)
        .await?
        .pop()
        .ok_or(AppError::not_found_with_option_msg(msg))
}
async fn query_row<T>(
    client: &impl GenericClient,
    sql: &str,
    params: &[&(dyn ToSql + Sync)],
) -> Result<T>
where
    T: FromTokioPostgresRow,
{
    let r = query_row_with_msg(client, sql, params, None).await?;
    Ok(r)
}

async fn execute(
    client: &impl GenericClient,
    sql: &str,
    params: &[&(dyn ToSql + Sync)],
) -> Result<u64> {
    let stmt = get_stmt(client, sql).await?;
    let aff_rows = client
        .execute(&stmt, params)
        .await
        .map_err(AppError::from)?;
    Ok(aff_rows)
}

async fn insert<T>(
    client: &impl GenericClient,
    sql: &str,
    params: &[&(dyn ToSql + Sync)],
) -> Result<T>
where
    T: FromTokioPostgresRow,
{
    let r = query_row_with_msg(client, sql, params, Some("插入失败")).await?;
    Ok(r)
}
